package org.dfl.plugin.editor.commands;

import java.awt.Window;
import java.util.Collection;
import java.util.List;
import java.util.Set;

import org.apache.log4j.Logger;
import org.dfl.core.data.types.MapType;
import org.dfl.core.data.types.Type;
import org.dfl.core.data.types.TypeManager;
import org.dfl.core.model.TInputEdge;
import org.dfl.core.model.TOutputEdge;
import org.dfl.core.model.transitions.TavernaTransition;
import org.dfl.plugin.DflPlugin;
import org.dfl.plugin.dialogs.TavernaProcesorPropertiesListener;
import org.dfl.plugin.editor.model.ConnectionModelElement;
import org.dfl.plugin.editor.model.TransitionModelElement;
import org.eclipse.gef.commands.Command;
import org.embl.ebi.escience.scufl.InputPort;
import org.embl.ebi.escience.scufl.OutputPort;
import org.embl.ebi.escience.scufl.Processor;
import org.embl.ebi.escience.scuflworkers.ProcessorEditor;
import org.jdom.Element;

/**
 * command that edit taverna processor properties
 * @author Piotr Wlodarczyk
 *
 */
public class TransitionEditProcesorCommand extends Command {
	private static Logger logger = Logger.getLogger(TransitionEditProcesorCommand.class);
	
	
	private TransitionModelElement tme;
	private Element oldTransition;
	private Element newTransition;
	private Set<TOutputEdge> oldOutputs;
	private Collection<TInputEdge> oldInputs;
	private Set<TOutputEdge> newOutputs;
	private Set<TInputEdge> newInputs;

	public TransitionEditProcesorCommand(TransitionModelElement tme) {
		super();
		this.tme = tme;
	}

	@Override
	public boolean canExecute() {
		if (!(tme.getTransition() instanceof TavernaTransition))
			return false;

		if(tme.getDiagram() == null || !tme.getDiagram().isEditable()) 
			return false;
		
		TavernaTransition t = (TavernaTransition) tme.getTransition();
		if (t.getProcessorEditor() == null)
			return false;

		return true;
	}

	@Override
	public void execute() {
		try {
			final TavernaTransition t = (TavernaTransition) tme.getTransition();
			oldTransition = t.save();
			oldOutputs = t.getOutputEdges();
			oldInputs = t.getInputEdges();

			final Processor theProcessor = t.getProcessor();
			final ProcessorEditor pe = t.getProcessorEditor();
			
			// use this listener as the one for the next execution
			DflPlugin.tavernaProcesorPropertiesListener = new TavernaProcesorPropertiesListener(
					tme.getDiagram()) {
				@Override
				public void tavernaProcesorPropertiesWindowClosed(Window window) {
					// there is no CancelCommandRuntimeException because nothing can catch it!
					// that's because it's not synchronized with editor window
					InputPort[] changedInputs = theProcessor.getInputPorts();
					OutputPort[] changedOutputs = theProcessor.getOutputPorts();
					
					List<ConnectionModelElement> conns = tme.getSourceConnections();
					for(ConnectionModelElement conn : conns)conn.setRefreshDiagramEnabled(false);
					conns = tme.getTargetConnections();
					for(ConnectionModelElement conn : conns)conn.setRefreshDiagramEnabled(false);
					
					for (TInputEdge e : oldInputs) e.disconnect();
					for (TOutputEdge e : oldOutputs) e.disconnect();
					
					for (InputPort i : changedInputs) {
							try {
								t.addInputDefinition(i.getName(), TypeManager
										.getTavernaType(i.getSyntacticType()));
							} catch (Exception e) {
								logger.error(e.getMessage(), e);
							}
					}

					Type oType = null;
					if (changedOutputs.length == 1) {
						// simple type - one output
						oType = TypeManager.getTavernaType(changedOutputs[0]
								.getSyntacticType());
					} else {
						MapType m = (MapType) TypeManager.getMapType();
						// complex type - many outputs
						for (int i = 0; i < changedOutputs.length; i++) {
							m.addSubkey(changedOutputs[i].getName(), TypeManager
									.getTavernaType(changedOutputs[i]
											.getSyntacticType()));
						}
						// fixed = true because if someone deleted needed output then we
						// have to remove connection
						m.setFixed(true);
						oType = m;
					}

					// fixed = false to compare
					t.disconnectAllOutputs();
					t.setOutputType(oType);
					t.calculateMinInputs();
					
					// reconnect inputs and outputs
					for (TInputEdge e : oldInputs) {
						try {
							e.reconnect();
						} catch (Exception e1) {}
					}
					for (TOutputEdge e : oldOutputs){
						try {
							e.reconnect();
						} catch (Exception e1) {}
					}

					conns = tme.getSourceConnections();
					for(ConnectionModelElement conn : conns)conn.setRefreshDiagramEnabled(true);
					conns = tme.getTargetConnections();
					for(ConnectionModelElement conn : conns)conn.setRefreshDiagramEnabled(true);
					
					newTransition = t.save();
					newOutputs = t.getOutputEdges();
					newInputs = t.getInputEdges();

					tme.modelStructureChanged();
					tme.refreshMarkers();
				}

				@Override
				public void tavernaProcesorPropertiesWindowOpened(Window window) {
				}
			};

			pe.getListener(theProcessor).actionPerformed(null);
			

		} catch (Exception e) {
			logger.error(e.getMessage(), e);
		}
	}
	
	@Override
	public boolean canUndo() {
		if(tme.getDiagram() == null || !tme.getDiagram().isEditable()) 
			return false;
		return super.canUndo();
	}

	@Override
	public void undo() {
		try {
			List<ConnectionModelElement> conns = tme.getSourceConnections();
			for(ConnectionModelElement conn : conns)conn.setRefreshDiagramEnabled(false);
			conns = tme.getTargetConnections();
			for(ConnectionModelElement conn : conns)conn.setRefreshDiagramEnabled(false);
			
			final TavernaTransition t = (TavernaTransition) tme.getTransition();
			t.load(oldTransition);
			for (TInputEdge e : newInputs) {
				e.disconnect();
			}
			for (TOutputEdge e : newOutputs) {
				e.disconnect();
			}

			for (TInputEdge e : oldInputs) {
				e.disconnect();
				e.reconnect();
			}
			for (TOutputEdge e : oldOutputs) {
				e.disconnect();
				e.reconnect();
			}

			conns = tme.getSourceConnections();
			for(ConnectionModelElement conn : conns)conn.setRefreshDiagramEnabled(true);
			conns = tme.getTargetConnections();
			for(ConnectionModelElement conn : conns)conn.setRefreshDiagramEnabled(true);
			
			tme.modelStructureChanged();
			tme.refreshMarkers();
		} catch (Exception e) {
			logger.error(e.getMessage(), e);
		}
	}

	@Override
	public void redo() {
		try {
			List<ConnectionModelElement> conns = tme.getSourceConnections();
			for(ConnectionModelElement conn : conns)conn.setRefreshDiagramEnabled(false);
			conns = tme.getTargetConnections();
			for(ConnectionModelElement conn : conns)conn.setRefreshDiagramEnabled(false);
			
			final TavernaTransition t = (TavernaTransition) tme.getTransition();
			t.load(newTransition);
			for (TInputEdge e : oldInputs) {
				e.disconnect();
			}
			for (TOutputEdge e : oldOutputs) {
				e.disconnect();
			}

			for (TInputEdge e : newInputs) {
				e.disconnect();
				e.reconnect();
			}
			for (TOutputEdge e : newOutputs) {
				e.disconnect();
				e.reconnect();
			}
			
			conns = tme.getSourceConnections();
			for(ConnectionModelElement conn : conns)conn.setRefreshDiagramEnabled(true);
			conns = tme.getTargetConnections();
			for(ConnectionModelElement conn : conns)conn.setRefreshDiagramEnabled(true);
			
			tme.modelStructureChanged();
			tme.refreshMarkers();
		} catch (Exception e) {
			logger.error(e.getMessage(), e);
		}
	}

}
